package controller;

import java.util.ArrayList;

import manager.BidMgr;
import manager.RoundMgr;
import controller.validator.Validator;
import entity.Bid;
import entity.Round;
import entity.Section;
import exception.InputValidationErrors;

/**
 * @author sheryl.tay.2012, mohameds.2012, huiwensong.2012, huangjm.2012
 * 
 */

public class RoundCtrl extends RoundClearingLogic {

	private RoundMgr roundMgr;
	private BidMgr bidMgr;
	private SectionStudentCtrl sectionStudentCtrl;

	public RoundCtrl() {
		roundMgr = new RoundMgr();
		bidMgr = new BidMgr();
		sectionStudentCtrl = new SectionStudentCtrl();
	}

	public int startRound() throws InputValidationErrors {

		if (!hasCurrentRoundBeenStarted()) {
			return startRound(Round.ROUND_ONE);
		} else if (isCurrentRoundOpen()) {
			throw new InputValidationErrors(Validator.ROUND_ALREADY_STARTED);
		} else if (isCurrentRoundNo(Round.ROUND_ONE)) {
			return startRound(Round.ROUND_TWO);
		} else {
			throw new InputValidationErrors(Validator.ROUND_2_ENDED);
		}

	}

	public int stopRound() throws InputValidationErrors {

		if (!hasCurrentRoundBeenStarted()) {
			throw new InputValidationErrors(Validator.ROUND_NOT_STARTED);
		} else if (isCurrentRoundOpen()) {
			processBids(getCurrentRound().getRoundNo());
			return stopRound(getCurrentRound().getRoundNo());
		} else {
			throw new InputValidationErrors(Validator.ROUND_ALREADY_ENDED);
		}

	}

	public Round getCurrentRound() {
		return roundMgr.retrieveCurrentRound();
	}

	// This logic is implemented right into SQL statement.
	// public String computeResultForBid(Bid bid) {
	// if (!isCurrentRoundOpen() || isCurrentRoundNo(Round.ROUND_ONE)) {
	// return bid.getBidResult();
	// } else if (isCurrentRoundNo(Round.ROUND_TWO)) {
	// String bidStatus = "";
	//
	// return bidStatus;
	// }
	// }

	public double getMinBidAmountForSection(Section section) {
		if (isCurrentRoundNo(Round.ROUND_ONE)) {
			return MIN_BID_AMOUNT;
		}

		double amount = roundMgr.retrieveMinBidAmountForSection(section
				.getCourse().getCourseCode(), section.getSectionCode());
		if (amount < MIN_BID_AMOUNT) {
			return MIN_BID_AMOUNT;
		}

		return amount;
	}

	public String getStatusForBid(Bid bid) {
		if (isCurrentRoundNo(Round.ROUND_ONE)
				|| (isCurrentRoundNo(Round.ROUND_TWO) && !isCurrentRoundOpen())) {
			return bid.getBidResult();
		}

		ArrayList<Bid> sortedBids = bidMgr
				.retrieveBidsForSectionOrderedByHighestBidAmount(bid
						.getSection().getCourse().getCourseCode(), bid
						.getSection().getSectionCode());
		int vacancy = sectionStudentCtrl.computeVacancyForSection(bid
				.getSection());

		int indexOfLastClearableBid = computeIndexOfLastClearableBid(
				sortedBids, vacancy, Round.ROUND_TWO);

		if (sortedBids.indexOf(bid) <= indexOfLastClearableBid) {
			return Bid.IN;
		}

		return Bid.OUT;
	}

	public boolean hasCurrentRoundBeenStarted() {
		return getCurrentRound().getStartDatetime() != null;
	}

	public boolean isCurrentRoundOpen() {
		return getCurrentRound().isRoundOpen();
	}

	public boolean isCurrentRoundNo(int roundNo) {
		return getCurrentRound().getRoundNo() == roundNo;
	}

	private int startRound(int roundNo) {
		/*
		 * Delay for 1 second before starting round 2. MySQL timestamp's
		 * precision is only till seconds and not microseconds. Not delaying the
		 * starting of round 2 might cause round 1 and round 2 start times to
		 * have same timestamp. Round start timestamp is used in determining
		 * which round the bids belong to.
		 */
		if (roundNo == Round.ROUND_TWO) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException ex) {
				Thread.currentThread().interrupt();
			}
		}

		if (roundMgr.startRound(roundNo)) {

			/*
			 * Clear bids from bid table made in previous round
			 */
			if (isCurrentRoundNo(Round.ROUND_TWO)) {
				bidMgr.reset();
			}

			return roundNo;
		}

		return 0;
	}

	private int stopRound(int roundNo) {
		if (roundMgr.stopRound(roundNo)) {
			return roundNo;
		}

		return 0;
	}

}
